package cz.cuni.amis.pogamut.episodic.visualizer;

import cz.cuni.amis.pogamut.episodic.decisions.*;
import cz.cuni.amis.pogamut.episodic.episodes.*;
import cz.cuni.amis.pogamut.episodic.memory.AgentMemory;
import cz.cuni.amis.pogamut.episodic.memory.Parameters;
import cz.cuni.amis.pogamut.episodic.schemas.*;
import java.awt.Color;
import java.awt.Dimension;
import java.util.*;

/**
 * <code>VisualizatonCreator</code> is a class connecting agent's mem with
 * the <code>VisualizationRenderer</code> module. It has access to the mem
 * structures and based on the mem contents it uses renderer to visualize
 * it. It uses renderer methods to display chronobags as separate graphs and
 * nodes as individual vertices.
 * <p>
 * For agent's mem it provides several simple methods that can take care
 * of visualization of different structures. Thus each time, new node is added
 * to some mem structure, a method can be called to redraw that structure
 * only, so that visualization always reflects current mem contents.
 *
 * @author Michal Cermak
 */
public class VisualizationCreator implements IVisualizationListener {
    /**
     * An instance of a renderer class used to perform any low level graphic
     * operations. Renderer class provides all functions the necessary to
     * display the visualizations of mem.
     */
    VisualizationRenderer visualizer = new VisualizationRenderer();

    /**
     * Counter used to provide unique ID to all the edges in visualized graphs.
     * Each edge in graph needs a unique ID, but it is not necessary to keep
     * record of these IDs. They are only needed when new edge is added into
     * the graph.
     */
    private int edgeIdCounter = 0;

    //values have to be continual starting from zero (graph chooser combo box returns index of combo item)
    final int DECISION_GRAPH_INDEX = 0;
    final int CHRONOBAG_VIEW_INDEX = 1;
    final int SCHEMA_GRAPH_INDEX = 2;
    final int PRESENT_GRAPH_INDEX = 3;
    final int PAST_GRAPH_START_INDEX = 4;

    /**
     * Used to remember number of past chronobags visualized last time they
     * were refreshed. If there are more chronobags in agent's past, it means
     * new chronobag was added, and chronobags have to be redrawn.
     */
    private Integer numberOfPastChronobags = 0;

    final Color COLOR_DEFAULT_VERTEX = Color.LIGHT_GRAY;
    final Color COLOR_INTENTION = Color.GREEN;
    final Color COLOR_ACTION = Color.RED;
    final Color COLOR_ATOMIC_ACTION = Color.ORANGE;
    final Color COLOR_AFFORDANCE = Color.DARK_GRAY;
    final Color COLOR_EDGE_STANDARD = Color.GRAY;
    final Color COLOR_EDGE_SEQUENCE = Color.BLUE;
    final Color COLOR_EPISODE_NODE = Color.LIGHT_GRAY;
    final Color COLOR_OBJECT = Color.BLUE;

    /**
     * Used to indicate if there is need for refreshing the visualization of
     * schema bag. Since nodes cannot be deleted from schema bag, this counter
     * of visualized nodes is enough to indicate if new node was added.
     */
    private int numberOfSchemaBagNodes = 0;

    /**
     * Variable used to determine where should object nodes be drawn.
     * It is calculated when episode nodes are displayed and corresponds to
     * the height of the deepest episode tree.
     */
    private int maxY = 0;

    public void handleVisualizationEvent(VisualizationEvent e) {
//        e.mem.sem.acquireUninterruptibly();
        switch (e.type) {
            case INITIALIZATION:
                //numberOfSchemaBagNodes = 0;
                createDecisionTree(e.mem.getDecisionTree());
                createChronobagView(e.mem);
                createSchemaBag(e.mem.schemas);
                refreshSchemaBag(e.mem.schemas);
                createPresentChronobag(e.mem.chrono.getPresentChronobag());
                refreshPastChronobags(e.mem.chrono.getPastChrononags());
                refreshPresentChronobag(e.mem.chrono.getPresentChronobag());
                break;
            case DECISION_TREE_UPDATE:
                createDecisionTree(e.mem.getDecisionTree());
                break;
            case REFRESH_CHRONOBAG_VIEW:
                refreshChronobagView(e.mem.schemas, e.mem.chrono.getChronobags());
                break;
            case REFRESH_PAST_CHRONOBAGS:
                refreshPastChronobags(e.mem.chrono.getPastChrononags());
                break;
            case REFRESH_PRESENT_CHRONOBAG:
                refreshPresentChronobag(e.mem.chrono.getPresentChronobag());
                break;
            case REFRESH_SCHEMA_BAG:
                refreshSchemaBag(e.mem.schemas);
                break;
            case UPDATE_TIME:
                visualizer.updateTime(e.time);
                break;
        }
//        e.mem.sem.release();
    }

    /**
     * Calling this method causes all the graphs visualizing the past
     * chronobags to refresh. If new chronobag was added to the list of
     * past chronobags, its visualization is created.
     *
     * @param past  List of past chronobags in agent's mem.
     */
    public void refreshPastChronobags(HashSet<Chronobag> past) {
        visualizer.removeGraph(PRESENT_GRAPH_INDEX);
        for (int index = 0; index < numberOfPastChronobags; index++) {
            visualizer.removeGraph(PAST_GRAPH_START_INDEX + index);
        }
        numberOfPastChronobags = past.size();
        visualizer.addGraph(PRESENT_GRAPH_INDEX, "Present day", new Dimension(1000,600));
        int index = 0;
        for (Chronobag c : past) {
            String strDaysAgo = "";
            strDaysAgo = c.begin + "-" + c.end + " day(s) ago";
            visualizer.addGraph(PAST_GRAPH_START_INDEX + index, strDaysAgo, new Dimension(1000,600));
            refreshChronobag(c, PAST_GRAPH_START_INDEX + index);
            index++;
        }
    }

    /**
     * Calling this method causes the visualization of the chronobag
     * representing current day to refresh.
     *
     * @param present   Reference to chronobag containing episodes from
     * current day.
     * @param time  String containing time information to be used as a label.
     * @param timeOnly  True if no new node was added to the chronobag, and
     * only updated part should be label with current time information.
     * False if whole chronobag needs to be redrawn.
     */
    public void refreshPresentChronobag(Chronobag present) {
        refreshChronobag(present, PRESENT_GRAPH_INDEX);
    }

    /**
     * Calling this method causes the visualization of specified chronobag
     * to refresh.
     *
     * @param present   Reference to chronobag that is to be refreshed.
     * @param time  String containing time information to be used as a label.
     * @param timeOnly  True if no new node was added to the chronobag, and
     * only updated part should be label with current time information.
     * False if whole chronobag needs to be redrawn.
     */
    public void refreshChronobag(Chronobag chronobag, int graphIndex) {
        if (!visualizer.refresh) return;

        visualizer.clearGraph(graphIndex);
        visualizer.addGraph(graphIndex, "", null);

        //episode trees
        for (Episode e : chronobag.getEpisodes()) {
            displayChronobagNode(graphIndex, e.getRoot());
        }

     //   visualizer.createTreeLayout(graphIndex);
        createTreeLayout(chronobag, graphIndex);

        //sequence of episodes
        for (Episode e1 : chronobag.getEpisodes()) {
            for (Episode e2 : e1.next) {
                int id1 = e1.getRoot().getId();
                int id2 = e2.getRoot().getId();
                visualizer.addEdge(graphIndex, edgeIdCounter++, EdgeType.SEQUENCE, e1.getRoot().getId(), e2.getRoot().getId(), "", COLOR_EDGE_SEQUENCE);
            }
        }
    }

    /**
     * To visualize a chronobag, this method should be called on each
     * of its top-level nodes. It is responsible for visualizing
     * the node as a vertex in a correct graph. Then it is recursively
     * called on each of node's child, thus correcly visualizing whole
     * chronobag. It also creates the edges between parent and child
     * nodes.
     *
     * @param graph Index of a graph, the node should be added to.
     * @param node  Reference to a node, that should be visualized.
     */
    private void displayChronobagNode(int graph, EpisodeNode node) {
        if (node == null) return;
        Color color = COLOR_EPISODE_NODE;
        VertexType vtype = VertexType.DEFAULT_VERTEX;
        if (node.getAssociatedNode() != null) {
            switch (node.getAssociatedNode().getType()) {
                case ACTION:
                    color = COLOR_ACTION;
                    vtype = VertexType.ACTION;
                    break;
                case INTENTION:
                    color = COLOR_INTENTION;
                    vtype = VertexType.INTENTION;
                    break;
                case ATOMIC_ACTION:
                    color = COLOR_ATOMIC_ACTION;
                    vtype = VertexType.ATOMIC_ACTION;
                    break;
            }
        }
        visualizer.addVertex(graph, node.getId(), node.getName(), color, vtype);
        String str = ((Integer)(node.getId())).toString()+" "+node.getName();
        visualizer.setVertexTooltip(node.getId(), str);
        visualizer.setVertexDetail(node.getId(), node.toString());
        visualizer.setVertexSaturation(node.getId(), node.getScore() / node.getEpisodeRoot().mem.parameters.MAX_NODE_SCORE);

        //visualizing affordances attached to the node
        for (ObjectSlot slot : node.getObjectSlots()) {
            visualizer.addVertex(graph, slot.getId(), slot.getType(), COLOR_AFFORDANCE, VertexType.AFFORDANCE);
            str = ((Integer)(slot.getId())).toString()+" "+slot.getType();
            visualizer.setVertexTooltip(slot.getId(), str);
            //visualizer.setVertexSaturation(slot.getId(), slot.getScore() / Parameters.MAX_NODE_SCORE);
            visualizer.addEdge(graph, edgeIdCounter++, node.getId(), slot.getId(), "", COLOR_EDGE_STANDARD);
            for (ObjectNode obj : slot.getUsedObjects()) {
                visualizer.addVertex(graph, obj.getId(), obj.getName(), COLOR_OBJECT, VertexType.OBJECT);
                str = ((Integer)(obj.getId())).toString()+" "+obj.getName();
                visualizer.setVertexTooltip(obj.getId(), str);
                visualizer.addEdge(graph, edgeIdCounter++, EdgeType.OBJECT, obj.getId(), slot.getId(), "", COLOR_EDGE_STANDARD);
            }
        }

        //recursive call on node's children and visualizing edges between node and its children
        for (EpisodeNode n : node.getChildrenNodes()) {
            displayChronobagNode(graph, n);
            visualizer.addEdge(graph, edgeIdCounter++, node.getId(), n.getId(), "", COLOR_EDGE_STANDARD);
        }

        //sequence edges between node siblings
        for (EpisodeNode n1 : node.getChildrenNodes()) {
            for (EpisodeNode n2 : n1.getSuccessor().values()) {
                if (n2 != null) {
                    visualizer.addEdge(graph, edgeIdCounter++, EdgeType.SEQUENCE, n1.getId(), n2.getId(), "", COLOR_EDGE_SEQUENCE);
                }
            }
        }
    }

    /**
     * This method is responsible for creating the graph that will hold
     * the vertices representing nodes of current chronobag. It should be
     * called only one, at the beginning of visualization process.
     *
     * @param bag   Reference to the current chronobag, so its nodes can
     * be displayed for the first time.
     */
    public void createPresentChronobag(Chronobag bag) {
        visualizer.addGraph(PRESENT_GRAPH_INDEX, "Present day", new Dimension(1000,600));

        refreshPresentChronobag(bag);
    }

    /**
     * To visualize a decision tree, this method should be called on each
     * of its top-level goals. It is responsible for visualizing
     * the node as a vertex in a correct graph. Then it is recursively
     * called on each of node's child, thus correcly visualizing whole
     * tree. It also creates the edges between parent and child nodes.
     * <p>
     * This method calls recursively similar method <code>displayActionTree</code>
     * that then calls this method again. It is because the children of
     * <code>Action</code> and <code>Intention</code> nodes are not of
     * the same type.
     *
     * @param graph Index of decision tree visualization graph.
     * @param i Reterence to the <code>Intention</code> that is to be visualized.
     * @return  Returns an ID of visualized node (the one in parameter).
     * @throws Exception    Can throw an exception if it tries to visualize
     * a node that does not have its ID set yet.
     * @see displayActionTree
     */
    private int displayIntentionTree(int graph, Intention i) throws Exception {
        visualizer.addVertex(graph, i.getId(), i.getName(), COLOR_INTENTION, VertexType.INTENTION);
        visualizer.setVertexTooltip(i.getId(), ((Integer)(i.getId())).toString()+" "+i.getName());

        for (AffordanceSlot slot : i.getAffordances()) {
            visualizer.addVertex(graph, slot.getId(), slot.getType(), COLOR_AFFORDANCE, VertexType.AFFORDANCE);
            visualizer.setVertexTooltip(slot.getId(), ((Integer)(slot.getId())).toString()+" "+slot.getType());
            visualizer.addEdge(graph, edgeIdCounter++, i.getId(), slot.getId(), "", COLOR_EDGE_STANDARD);
        }
        int id;
        for (Action a : i.getSubNodes()) {
            id = displayActionTree(graph, a);
            visualizer.addEdge(graph, edgeIdCounter++, i.getId(), id, "", COLOR_EDGE_STANDARD);
        }
        return i.getId();
    }

    /**
     * To visualize a decision tree, this method is called on each
     * of its action nodes. It is responsible for visualizing
     * the node as a vertex in a correct graph. Then it is recursively
     * called on each of node's child, thus correcly visualizing whole
     * tree. It also creates the edges between parent and child nodes.
     * <p>
     * This method is recursively called by similar method <code>displayActionTree</code>
     * and then calls this method again. It is because the children of
     * <code>Action</code> and <code>Intention</code> nodes are not of
     * the same type.
     *
     * @param graph Index of decision tree visualization graph.
     * @param a Reterence to the <code>Action</code> that is to be visualized.
     * @return  Returns an ID of visualized node (the one in parameter).
     * @throws Exception    Can throw an exception if it tries to visualize
     * a node that does not have its ID set yet.
     * @see displayIntentionTree
     */
    private int displayActionTree(int graph, Action a) throws Exception{
        visualizer.addVertex(graph, a.getId(), a.getName(), COLOR_ACTION, VertexType.ACTION);
        visualizer.setVertexTooltip(a.getId(), ((Integer)(a.getId())).toString()+" "+a.getName());

        int id;
        for (AtomicAction aa : a.getAtomicActions()) {
            visualizer.addVertex(graph, aa.getId(), aa.getName(), COLOR_ATOMIC_ACTION, VertexType.ATOMIC_ACTION);
            visualizer.setVertexTooltip(aa.getId(), ((Integer)(aa.getId())).toString()+" "+aa.getName());
            visualizer.addEdge(graph, edgeIdCounter++, a.getId(), aa.getId(), "", COLOR_EDGE_STANDARD);
        }
        for (AffordanceSlot slot : a.getAffordances()) {
            visualizer.addVertex(graph, slot.getId(), slot.getType(), COLOR_AFFORDANCE, VertexType.AFFORDANCE);
            visualizer.setVertexTooltip(slot.getId(), ((Integer)(slot.getId())).toString()+" "+slot.getType());
            visualizer.addEdge(graph, edgeIdCounter++, a.getId(), slot.getId(), "", COLOR_EDGE_STANDARD);
        }
        for (Intention i : a.getSubNodes()) {
            id = displayIntentionTree(graph, i);
            visualizer.addEdge(graph, edgeIdCounter++, a.getId(), id, "", COLOR_EDGE_STANDARD);
        }
        return a.getId();
    }

    /**
     * This method is responsible for creating the graph that will hold
     * the vertices representing nodes of decision tree. It should be
     * called only one, at the beginning of visualization process.
     *
     * @param dTree Reference to the decision tree, so its nodes can
     * be displayed.
     */
    public void createDecisionTree(DecisionTree dTree) {
        if (visualizer.graphs.get(DECISION_GRAPH_INDEX) == null) {
            visualizer.addGraph(DECISION_GRAPH_INDEX, "Decision Tree", new Dimension(1000,600));
        } else {
            visualizer.clearGraph(DECISION_GRAPH_INDEX);
        }
        try {
            for (Intention i : dTree.topLevelGoals.values()) {
                displayIntentionTree(DECISION_GRAPH_INDEX, i);
            }

            visualizer.createTreeLayout(DECISION_GRAPH_INDEX);

        } catch (Exception e) {
            //TODO: delete partial visualization
            System.err.println("Could not visualize decision graph.");
            System.err.println(e.getMessage());
        }
    }

    /**
     * This method is responsible for creating the graph that will hold
     * the vertices representing individual chronobags. It should be
     * called only one, at the beginning of visualization process.
     *
     * @param schemas   Reference to the schema bag so a vertex representing
     * schema bag can be added to the visualization with correct additional
     * information.
     * @param present   Reference to the current chronobagso a vertex representing
     * it can be added to the visualization with correct additional information.
     */
    public void createChronobagView(AgentMemory mem) {
        SchemaBag schemas = mem.schemas;
        Chronobag present = mem.chrono.getPresentChronobag();
        visualizer.addGraph(CHRONOBAG_VIEW_INDEX, "Chronobags", new Dimension(1000,600));
        visualizer.clearGraph(CHRONOBAG_VIEW_INDEX);

        visualizer.addVertex(CHRONOBAG_VIEW_INDEX, schemas.id, "Schema Bag", COLOR_OBJECT, VertexType.DEFAULT_VERTEX);
        visualizer.setVertexTooltip(schemas.id, ((Integer)schemas.id).toString() + " Schema bag");
        visualizer.setVertexDetail(schemas.id, schemas.toString());

        visualizer.addVertex(CHRONOBAG_VIEW_INDEX, present.id, "Present day", COLOR_DEFAULT_VERTEX, VertexType.DEFAULT_VERTEX);
        visualizer.setVertexTooltip(present.id, ((Integer)present.id).toString() + " Present day");
        visualizer.setVertexDetail(present.id, present.toString());

        for (Chronobag c : mem.chrono.getPastChrononags()) {
            visualizer.addVertex(CHRONOBAG_VIEW_INDEX, c.id, c.begin + "-" + c.end, COLOR_DEFAULT_VERTEX, VertexType.DEFAULT_VERTEX);
            visualizer.setVertexTooltip(c.id, ((Integer)c.id).toString());
            visualizer.setVertexDetail(c.id, c.toString());
            if (c.older != null) {
                visualizer.addEdge(CHRONOBAG_VIEW_INDEX, edgeIdCounter++, c.id, c.older.id, "", COLOR_EDGE_SEQUENCE);
            }
        }

        visualizer.createTreeLayout(CHRONOBAG_VIEW_INDEX);
    }

    /**
     * This method is responsible for creating the graph that will hold
     * the vertices representing nodes of a schema bag. It should be
     * called only one, at the beginning of visualization process.
     * <p>
     * This method also sets a reference to <code>SchemaMessageCommand</code>
     * class so that each time a counter is increased, the visualization
     * does not have to be refreshed, but actual count will be provided via
     * the call to <code>SchemaMessageCommand</code>'s method
     * <code>getSchemaMessage</code>.
     *
     * @param schemas Reference to the schema bag, so its nodes can
     * be displayed.
     */
    public void createSchemaBag(SchemaBag schemas) {
        visualizer.addGraph(SCHEMA_GRAPH_INDEX, "Schemas", new Dimension(1000,600));
        visualizer.setSchemaMessageCommand(schemas.schemaMessageCommand, SCHEMA_GRAPH_INDEX);
        numberOfSchemaBagNodes = 0; //schemas.getNumberOfNodes();
    }

    /**
     * Calling this method causes the visualization of a schema bag to refresh.
     * <p>
     * Unless new node was added to the schema bag it only updates the current
     * time information label. Actual counts will still be up to date because
     * they will be provided via the call to <code>SchemaMessageCommand</code>'s
     * method <code>getSchemaMessage</code>.
     *
     * @param schemas   Reference to a schema bag so that it can be refreshed.
     * @param time  String containing time information to be used as a label.
     */
    public void refreshSchemaBag(SchemaBag schemas) {
        if (!visualizer.refresh) return;
        if (schemas.getNumberOfNodes() == numberOfSchemaBagNodes) return;
        numberOfSchemaBagNodes = schemas.getNumberOfNodes();

        for (SchemaEpisodeNode node : schemas.getSchemaENodes()) {
            displaySchemaEpisodeNode(node);
        }

        for (SchemaObjectNode node : schemas.getSchemaONodes()) {
            displaySchemaObjectNode(node);
        }

        visualizer.createTreeLayout(SCHEMA_GRAPH_INDEX);
    }

    /**
     * Calling this method causes the visualization of a chronobag overview
     * to refresh. It adds newly created chronobags to it and updates
     * the chronobag summary information.
     *
     * @param schemas   Reference to a schema bag so that its summary
     * can be refreshed.
     * @param present   Reference to a current chronobag so that its summary
     * can be refreshed.
     * @param past  Reference to a list of past chronobags. If new chronobag
     * was added there it is added to the visualization too. Summary of all
     * chronobags are also updated.
     */
    public void refreshChronobagView(SchemaBag schemas, Collection<Chronobag> chronobags) {
        if (!visualizer.refresh) return;

        visualizer.setVertexDetail(schemas.id, schemas.toString());

        String s;
        for (Chronobag c : chronobags) {
            s = c.begin + "-" + c.end;
            visualizer.addVertex(CHRONOBAG_VIEW_INDEX, c.id, s, COLOR_DEFAULT_VERTEX, VertexType.DEFAULT_VERTEX);
            visualizer.setVertexTooltip(c.id, ((Integer)c.id).toString() + " " + s);
            visualizer.setVertexDetail(c.id, c.toString());
            if (c.older != null) {
                if (visualizer.addEdge(CHRONOBAG_VIEW_INDEX, edgeIdCounter++, EdgeType.SUBNODE, c.id, c.older.id, "", COLOR_EDGE_SEQUENCE)) {
                    edgeIdCounter++;
                }
            }
        }

        visualizer.createTreeLayout(CHRONOBAG_VIEW_INDEX);
    }

    /**
     * Refreshes a specified schema object node in schema bag visualization.
     * If the node was not visualized yet, it is added to the visualization
     * graph. It is then connected with all the slot contents vertices, it
     * is linked with in actual schema bag.
     *
     * @param node  Reference to a <code>SchemaObjectNode</code> that is to
     * be visualized.
     */
    private void displaySchemaObjectNode(SchemaObjectNode node) {
        if (node == null) return;
        visualizer.addVertex(SCHEMA_GRAPH_INDEX, node.getId(), node.getName(), COLOR_OBJECT, VertexType.OBJECT);
        String str = ((Integer)(node.getId())).toString()+" "+node.getName();
        visualizer.setVertexTooltip(node.getId(), str);
        for (SlotContent content : node.getSlotContents()) {
            visualizer.addEdge(SCHEMA_GRAPH_INDEX, edgeIdCounter++, EdgeType.OBJECT, node.getId(), content.getId(), "", COLOR_EDGE_STANDARD);
        }
    }

    /**
     * Refreshes a specified schema episode node in schema bag visualization.
     * If the node was not visualized yet, it is added to the visualization
     * graph. It is then connected with all the slots and slot contents
     * vertices, it is linked with in actual schema bag.
     * <p>
     * Unlike similar methods for chronobag nodes and decision nodes, this
     * method does not call itself recursively and has to be called for
     * each node separately.
     *
     * @param node  Reference to a <code>SchemaEpisodeNode</code> that is to
     * be visualized.
     */
    private void displaySchemaEpisodeNode(SchemaEpisodeNode node) {
        if (node == null) return;
        Color color = COLOR_EPISODE_NODE;
        VertexType vtype = VertexType.DEFAULT_VERTEX;
        if (node.getAssociatedNode() != null) {
            switch (node.getAssociatedNode().getType()) {
                case ACTION:
                    color = COLOR_ACTION;
                    vtype = VertexType.ACTION;
                    break;
                case INTENTION:
                    color = COLOR_INTENTION;
                    vtype = VertexType.INTENTION;
                    break;
                case ATOMIC_ACTION:
                    color = COLOR_ATOMIC_ACTION;
                    vtype = VertexType.ATOMIC_ACTION;
                    break;
            }
        }
        visualizer.addVertex(SCHEMA_GRAPH_INDEX, node.getId(), node.getName(), color, vtype);
        String str = ((Integer)(node.getId())).toString()+" "+node.getName();
        visualizer.setVertexTooltip(node.getId(), str);

        SchemaEpisodeNode parent = null;
        if (node.getAssociatedNode().parent != null) {
            parent = node.getAssociatedNode().parent.getAssociatedNode();
        }
        if (parent != null) {
            visualizer.addEdge(SCHEMA_GRAPH_INDEX, edgeIdCounter++, parent.getId(), node.getId(), "", COLOR_EDGE_STANDARD);
        }

        for (SchemaSlot slot : node.getSlots()) {
            visualizer.addVertex(SCHEMA_GRAPH_INDEX, slot.getId(), slot.getType(), COLOR_AFFORDANCE, VertexType.AFFORDANCE);
            str = ((Integer)(slot.getId())).toString()+" "+slot.getType();
            visualizer.setVertexTooltip(slot.getId(), str);
            visualizer.addEdge(SCHEMA_GRAPH_INDEX, edgeIdCounter++, node.getId(), slot.getId(), "", COLOR_EDGE_STANDARD);

            for (SlotContent content : slot.getSlotContents()) {
                visualizer.addVertex(SCHEMA_GRAPH_INDEX, content.getId(), "(slot content)", COLOR_AFFORDANCE, VertexType.AFFORDANCE);
                str = ((Integer)(content.getId())).toString() + "\n";
                str += " slot:" + ((Integer)(content.getSlot().getId())).toString() + " " + content.getSlot().getType() + "\n";
                str += " object:" + ((Integer)(content.getObject().getId())).toString() + " " + content.getObject().getName();
                visualizer.setVertexTooltip(content.getId(), str);
                visualizer.addEdge(SCHEMA_GRAPH_INDEX, edgeIdCounter++, slot.getId(), content.getId(), "", COLOR_EDGE_STANDARD);
            }
        }
     }

    public void createTreeLayout(Chronobag c, int graph) {
        maxY = 0;
        Episode e = c.getFirstEpisode();
        if (e == null) return;
        int x = 0;
        Set<Episode> done = new HashSet<Episode>();
        while (!done.contains(e)) {
            x += setNodeTreePosition(graph, e.getRoot(), x, 0);
            done.add(e);
            Iterator<Episode> i = e.next.iterator();
            while (i.hasNext()) {
                e = i.next();
                if (!done.contains(e)) break;
            }
        }
        visualizer.lastNonObjectNode.put(graph, maxY);
        visualizer.positionObjectNodes(maxY + c.chrono.mem.parameters.NODES_Y_MARGIN);
    }

    private int setNodeTreePosition(int graph, EpisodeNode node, int x, int y) {
        Queue<EpisodeNode> nodes = new LinkedList<EpisodeNode>();
        Set<Integer> included = new HashSet<Integer>();
        Stack<EpisodeNode> s = new Stack<EpisodeNode>();
        for (EpisodeNode n : node.getFirstChild().values()) {
            s.push(n);
        }
        EpisodeNode n;

        while (!s.isEmpty()) {
            n = s.pop();
            if (included.contains(n.getId())) continue;
            included.add(n.getId());
            nodes.add(n);
            for (EpisodeNode suc : n.getSuccessor().values()) {
                s.push(suc);
            }
        }

        int size = 0;
        if (nodes.isEmpty()) {
            size = node.getEpisodeRoot().mem.parameters.NODES_X_MARGIN;
        }
        while (!nodes.isEmpty()) {
            n = nodes.poll();
            size += setNodeTreePosition(graph, n, x + size, y + node.getEpisodeRoot().mem.parameters.NODES_Y_MARGIN);
        }
        for (ObjectSlot slot : node.getObjectSlots()) {
            visualizer.setVertexLocation(graph, slot.getId(), x + size + node.getEpisodeRoot().mem.parameters.NODES_X_MARGIN / 2, y + 0.8 * node.getEpisodeRoot().mem.parameters.NODES_Y_MARGIN);
            size += node.getEpisodeRoot().mem.parameters.NODES_X_MARGIN;
        }
        visualizer.setVertexLocation(graph, node.getId(), x + size / 2, y);
        if (y > maxY) maxY = y;
        return size;
    }

    public static void main(String[] args)
    {
        VisualizationRenderer viz = new VisualizationRenderer();
        viz.addGraph(0, "G1", new Dimension(300,300));
        viz.addGraph(1, "G2", new Dimension(1000,600));

        viz.addEdge(0, 51, 41, 42, "", Color.red);
        viz.addEdge(0, 52, 42, 43, "", Color.red);
        viz.addEdge(0, 53, 42, 44, "", Color.blue);
        viz.addEdge(0, 54, 42, 44, "", Color.green);

        viz.addVertex(1, 0, 0, 0, "a", Color.BLUE);
        viz.addVertex(1, 1, 100, 100, "b", Color.RED);
        viz.addVertex(1, 2, 400, 100, "c", Color.GREEN);
        viz.addVertex(1, 3, 1000, 600, "d", Color.ORANGE);
        viz.addVertex(0, 41, "nopos1");
        viz.addVertex(0, 42, "nopos2");
        viz.addVertex(0, 43, "nopos3");
        viz.addVertex(0, 44, "nopos4");
        viz.addVertex(0, 45, "nopos5");

        viz.addEdge(1, 0, 0, 1, "e", Color.red);
        viz.setVertexTooltip(0, "tooltip");
        viz.addVertex(0, 7, 150, 150, "g1vertex", Color.BLUE);
        viz.addVertex(0, 71, 250, 250, "g1vertex1", Color.BLUE);
        viz.addVertex(1, 8, 150, 150, "g2vertex", Color.ORANGE);
        viz.createTreeLayout(0);
        viz.createTreeLayout(1);
    }
}
